import os
from collections.abc import AsyncIterator
from dataclasses import dataclass
from typing import Any
from unittest.mock import AsyncMock

import pytest
from fastapi import FastAPI
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import (
    AsyncSession,
    create_async_engine,
    async_sessionmaker,
    AsyncEngine,
)

from recruit.adapter.database.models import Base
from recruit.application.protocols import UoW
from recruit.main import create_app as create_fastapi_app

pytest_plugins = [
    'tests.adapter.models.fixtures.resume',
    'tests.adapter.models.fixtures.user',
    'tests.adapter.gateway.fixtures.user',
    'tests.adapter.gateway.fixtures.resume'
]


@pytest.fixture
def create_app() -> FastAPI:
    return FastAPI()


@dataclass
class TestsConfig:
    db_url: str = os.getenv('DB_URI_TEST')


def load_config() -> dict[str, Any]:
    return {
        'DB_URI': TestsConfig.db_url
    }


@pytest.fixture
def set_environ() -> None:
    for key, value in load_config().items():
        os.environ[key] = value


def get_engine() -> AsyncEngine:
    return create_async_engine(load_config()['DB_URI'])


def create_async_session_maker() -> async_sessionmaker[AsyncSession]:
    return async_sessionmaker(get_engine())


@pytest.fixture(autouse=True)
async def init_db() -> AsyncIterator[None]:
    engine = get_engine()
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)
    yield
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.drop_all)


@pytest.fixture
async def db_session() -> AsyncIterator[AsyncSession]:
    async_session_maker = create_async_session_maker()
    async with async_session_maker() as session:
        yield session


@pytest.fixture
async def app_client(db_session: AsyncSession, set_environ: None) -> None:
    app = create_fastapi_app()
    app.dependency_overrides[AsyncSession] = lambda: db_session
    async with AsyncClient(app=app, base_url='http://test') as client:
        yield client


@pytest.fixture
async def mock_uow() -> UoW:
    uow = AsyncMock()
    uow.commit = AsyncMock()
    uow.flush = AsyncMock()
    uow.rollback = AsyncMock()
    return uow
